/*
	calibrate_APIC_timer_64.c

	contains functionality for calibrating the APIC timer.
	gets the number of APIC timer ticks per second

	Author: Aidan Goddard, 14/5/13
*/

#include "../kernel/headers/types.h"
#include "../kernel/headers/system_config_tables.h"
#include"headers/printf.h"
#include"headers/IRQ.h"
#include"headers/ISR.h"

// global timer ticks value
uint32_t g_ticks = 0;

// IRQ headers
void IRQ_spurious_APIC();
void IRQ_dummy_APIC();
void IRQ_APIC_PIT_calibrator();

/*
	CalibrateAPICTimer

	no params

	returns the number of ticks per second the APIC timer operates at

	This function sets up the PIT to a known frequency and enables its
	IRQ which points to a legal ISR. It then calibrates the APIC chip.

	After this it waits for the first PIT IRQ to fire (which sets up
	the APIC timer and starts it) and then the second one (which gets
	the number of APIC ticks passes) before calculating how many ticks
	are counted each second and returning it.

	NOTE: use of PIT will be replaced with the HPET in future versions
	in an attempt to phase out the use of legacy hardware.
*/
unsigned int CalibrateAPICTimer()
{
	// print hello message
	Printf("\n[ APIC  ] begining timer calibration");

	// setup PIC to map IRQs to 0x20
	PICSetup(0x20);

	// unmask PIT (PIC IRQ 0)
	PICUnmaskIRQ(0);

	// program PIT for 100hz (ish)
	double PIT_frequency = PITSetup(100);
	Printf("\n[ APIC  ] PIT frequency: %fHz", PIT_frequency);

	// set ISR 0x20 to the calibrator ISR
	RegisterISR(0x20, (uint64_t)&IRQ_APIC_PIT_calibrator, 0x08, CPL_0, GATE_INTERRUPT, IST_1);

	// program APIC timer to ISR 0x40 (vector is set in PIT calibrator)
	// get APIC base address from APIC MSR (and hardware enable it)
	uint32_t apic_low, apic_high;
	rdmsr(0x1b, &apic_low, &apic_high);		// get msr contents
	apic_low = apic_low | 0x800;			// set hardware enable bit
	wrmsr(0x1b, apic_low, apic_high);		// write msr contents
	volatile uint32_t *APIC_base = (uint32_t*)(uint64_t)(apic_low & 0xfffff000);

	// set APIC spurious interrupt register
	__asm__ __volatile__("":::"memory");
	uint32_t APIC_spurious = *(APIC_base + (0xf0/4));	// get contents of spurious interrupt register
	__asm__ __volatile__("":::"memory");
	APIC_spurious = APIC_spurious | 0x100;		// set software enable bit
	APIC_spurious = APIC_spurious & 0xffffff00;	// clear vector
	APIC_spurious = APIC_spurious + 0x4f;		// set vector to 0x4f
	__asm__ __volatile__("":::"memory");
	*(APIC_base + (0xf0/4)) = APIC_spurious;	// set new contents of spurious interrupt register
	__asm__ __volatile__("":::"memory");

	// set APIC timer vector register
	__asm__ __volatile__("":::"memory");
	*(APIC_base + (0x320/4)) = 0x40;
	__asm__ __volatile__("":::"memory");

	// set the APIC timer divide register to 16
	__asm__ __volatile__("":::"memory");
	*(APIC_base + (0x3e0/4)) = 3;
	__asm__ __volatile__("":::"memory");

	// set ISR 0x40 to APIC dummy and ISR 0x41 to APIC spurious
	RegisterISR(0x40, (uint64_t)&IRQ_dummy_APIC, 0x08, CPL_0, GATE_INTERRUPT, IST_1);
	RegisterISR(0x4f, (uint64_t)&IRQ_spurious_APIC, 0x08, CPL_0, GATE_INTERRUPT, IST_1);

	// enable interrupts and halt
	__asm__ __volatile__("sti; hlt":::"memory");

	// first interrupt has fired and started the APIC timer, halt wait for the second one, then clear interrupts and continue
	__asm__ __volatile__("hlt; cli":::"memory");

	// second PIT timer has fired, get ticks and calculate ticks per s
	unsigned int ticks = (0x7fffffff - g_ticks) * 16;	// get number of ticks passed between the PIT IRQs
	ticks = ticks * ((unsigned int)PIT_frequency);		// number of ticks per second

	// disable all ISRs
	DisableISR(0x20);
	DisableISR(0x40);
	DisableISR(0x4f);

	// disable APIC timer
	__asm__ __volatile__("":::"memory");
	*(APIC_base + (0x320/4)) = 0x10000;
	__asm__ __volatile__("":::"memory");

	// disable PIC
	PICDisable();

	// print number of ticks
	Printf("\n[ APIC  ] APIC timer ticks per 1s: %u", ticks);

	// save it to the system table
	SetAPICTimerBusSpeed(ticks);

	// finished, return ticks
	return ticks;
}
